package cn.rayland.library.utils;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.Semaphore;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import cn.rayland.api.Cura;
import cn.rayland.api.Gpx;
import cn.rayland.api.Machine;
import cn.rayland.api.SliceCallback;
import cn.rayland.api.X3gStatus;
import cn.rayland.library.bean.SliceSetting;

/**
 * convert utils, convert stl to gcode, gcode to x3g
 * @author gw
 *
 */
public class ConvertUtils {
	private static final String TAG = ConvertUtils.class.getSimpleName();
	public static final String TMP_STL = "tmpStl";
	public static final String TMP_GCODE = "tmpGcode";
	public static final String TMP_X3G = "tmpX3g";
	private static final String SLICE_URL = "http://ray-land.cn:4000/uploadWithQ";
	private static final int READ_TIME_OUT = 8 * 60 * 1000;
	private static final int CONN_TIME_OUT = 8 * 1000;
	private static final String CHARSET = "utf-8";
	private static Handler handler = new Handler(Looper.getMainLooper());
	public static final String DOWNLOAD_STL = "download_stl";
	public static final String UPLOAD_STL = "upload_stl";
	public static final String STL_TO_GCODE = "stl_to_gcode";
	public static final String GCODE_TO_X3G = "gcode_to_x3g";
	public static final String EDIT_STL = "edit_stl";
	public static final String PROGRESS_START = "start";
	public static final String PROGRESS_SLICE = "slice";
	public static final String PROGRESS_LAYERPARTS = "layerparts";
	public static final String PROGRESS_INSET = "inset";
	public static final String PROGRESS_SUPPORT = "support";
	public static final String PROGRESS_SKIN = "skin";
	public static final String PROGRESS_EXPORT = "export";
	
	
	
	public static volatile boolean cancelConvert = false;
	private static final int BUFFER_SIZE = 1024 * 8;

	public static void stlToGcode(final Context context, final File file, final SliceSetting setting,
			final Machine machine, final Callback callback) {
		if (setting != null && file != null && file.exists()) {
			new Thread() {
				public void run() {
					String startCode = "M136\n" + "M73 P0\n" + "G130 X"
							+ machine.axis_x.driving_voltage + " Y"
							+ machine.axis_y.driving_voltage + " Z"
							+ machine.axis_z.driving_voltage + " A"
							+ machine.extruder_a.driving_voltage + " B"
							+ machine.extruder_b.driving_voltage + "\n"
							+ "G161 X F2000\n" + "G161 Y F2000\n"
							+ "G161 Z F2000\n" + "G92 X"
							+ (-machine.axis_x.length / 2) + " Y"
							+ (-machine.axis_y.length / 2) + " Z-5 A0 B0\n"
							+ "G1 Z0 F1000\n" + "G161 Z F500\n" + "G92 X"
							+ (-machine.axis_x.length / 2) + " Y"
							+ (-machine.axis_y.length / 2) + " Z0 A0 B0\n"
							+ "G1 E-6 F2000\n" + "G1 X0 Y0 Z5 F1000\n"
							+ "M109 S" + setting.getTemp() + "\n"
							+ "G1 E0 F2000\n" + "M135 T0\n" + "M133 T0\n";
					File gcodeFile = new File(PathUtils.getSliceFolder(),
							TMP_GCODE);
					if (setting.getEngine().toLowerCase(Locale.ENGLISH).contains("local")) {
						stlToGcodeByLocal(context, file, gcodeFile, startCode, setting,
								callback);
					} else {
						stlToGcodeByCloud(file, gcodeFile, startCode, setting,
								callback);
					}

				};
			}.start();
		}
	}

	/**
	 * 将Stl文件转化为Gcode文件
	 * 
	 * @param file
	 *            stl文件
	 * @param setting
	 *            切片设置
	 * @param callback
	 *            切片完成的回调
	 */
	public static void stlToGcodeByCloud(final File file,
			final File downloadFile, String startCode,
			final SliceSetting setting, final Callback callback) {
		if (file == null)
			return;
		if (callback != null) {
			handler.post(new Runnable() {
				public void run() {
					callback.onPreConvert();
				}
			});
		}
		Log.d(TAG, "start upload " + file.getAbsolutePath());
		cancelConvert = false;
		String BOUNDARY = UUID.randomUUID().toString(); // 边界标识 随机生成
		String PREFIX = "--", LINE_END = "\r\n";
		String CONTENT_TYPE = "multipart/form-data"; // 内容类型

		try {
			URL url = null;
			if (setting == null) {
				url = new URL(SLICE_URL);
			} else {
				url = new URL(SLICE_URL + setting.toParams());
			}
			FileInputStream is = new FileInputStream(file);
			StringBuffer sb = new StringBuffer();
			sb.append(PREFIX);
			sb.append(BOUNDARY);
			sb.append(LINE_END);
			sb.append("Content-Disposition: form-data; name=\"upload\"; filename=\""
					+ file.getName() + "\"" + LINE_END);
			sb.append("Content-Type: application/octet-stream; charset="
					+ CHARSET + LINE_END);
			sb.append(LINE_END);
			byte[] end_data = (PREFIX + BOUNDARY + PREFIX + LINE_END)
					.getBytes();
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			conn.setReadTimeout(READ_TIME_OUT);
			conn.setConnectTimeout(CONN_TIME_OUT);
			conn.setChunkedStreamingMode(BUFFER_SIZE);
			conn.setDoInput(true); // 允许输入流
			conn.setDoOutput(true); // 允许输出流
			conn.setUseCaches(false); // 不允许使用缓存
			conn.setRequestMethod("POST"); // 请求方式
			conn.setRequestProperty("Charset", CHARSET); // 设置编码
			conn.setRequestProperty("Connection", "Keep-Alive");
			conn.setRequestProperty("Content-Type", CONTENT_TYPE + ";boundary="
					+ BOUNDARY);

			// 当文件不为空，把文件包装并且上传
			DataOutputStream dos = new DataOutputStream(conn.getOutputStream());

			dos.write(sb.toString().getBytes());
			byte[] bytes = new byte[BUFFER_SIZE];
			int lastProgress = 0;
			int len = -1;
			while ((len = is.read(bytes)) != -1 && !cancelConvert) {
				dos.write(bytes, 0, len);
				final int progress = is.getChannel().size() > 0 ? (int) ((1 - (is
						.available() * 1.0f / is.getChannel().size())) * 100)
						: -1;
				if (progress != lastProgress) {
					Log.d(TAG, "upload  " + progress + "%");
					if (callback != null) {
						handler.post(new Runnable() {
							public void run() {
								callback.onConvertProgress(UPLOAD_STL, progress);
							}
						});
					}
				}
				lastProgress = progress;
			}
			is.close();
			dos.write(LINE_END.getBytes());
			dos.write(end_data);
			dos.flush();
			dos.close();

			if (conn.getResponseCode() == 200) {
				InputStream inputStream = conn.getInputStream();
				int fileLength = conn.getContentLength();
				Log.d(TAG, "fileLength = " + fileLength);
				int downloadLength = 0;
				FileOutputStream fos = new FileOutputStream(downloadFile);
				BufferedOutputStream bos = new BufferedOutputStream(fos);
				byte[] buffer = new byte[BUFFER_SIZE];
				int length = -1;
				int last = 0;
				// bos.write(startCode.getBytes());
				while ((length = inputStream.read(buffer)) != -1
						&& !cancelConvert) {
					bos.write(buffer, 0, length);
					downloadLength += length;
					final int progress = (int) (fileLength > 0 ? (downloadLength * 1.0f / fileLength) * 100
							: -1);
					if (progress != last) {
						Log.d(TAG, "download  " + progress + "%");
						if (callback != null) {
							handler.post(new Runnable() {
								public void run() {
									callback.onConvertProgress(DOWNLOAD_STL,
											progress);
								}
							});
						}
					}
					last = progress;
				}
				closeStream(is);
				closeStream(bos);
				closeStream(fos);
				if (callback != null) {
					handler.post(new Runnable() {
						public void run() {
							callback.onConvertSuccess(downloadFile, null);
						}
					});
				}

			} else {
				InputStream inputStream = conn.getErrorStream();
				final StringBuilder stringBuilder = new StringBuilder();
				int i;
				while ((i = inputStream.read()) != -1) {
					stringBuilder.append((char) i);
				}
				inputStream.close();
				if (callback != null) {
					handler.post(new Runnable() {
						public void run() {
							callback.onConvertFailed(stringBuilder.toString());
						}
					});
				}

			}
			conn.disconnect();
		} catch (final Exception e) {
			e.printStackTrace();
			if (callback != null) {
				handler.post(new Runnable() {
					public void run() {
						callback.onConvertFailed(e.getMessage());
					}
				});
			}
		}
	}

	public static void stlToGcodeByLocal(final Context context, final File file, final File gcodeFile,
			final String startCode, final SliceSetting setting,
			final Callback callback) {
		if (callback != null) {
			handler.post(new Runnable() {
				public void run() {
					callback.onPreConvert();
				}
			});
		}
		cancelConvert = false;
		final Semaphore semaphore = new Semaphore(-1);

		final File newStlFile = new File(PathUtils.getEditorFolder(), TMP_STL);
		new Thread() {
			public void run() {
				if (callback != null && !cancelConvert) {
					handler.post(new Runnable() {
						public void run() {
							callback.onConvertProgress(EDIT_STL, 100);
						}
					});
				}
				StlEditor.editStlFile(file, newStlFile, setting.getOffset_x(),
						setting.getOffset_y(), setting.getOffset_z(),
						setting.getScale(), setting.getRotate_z());
				semaphore.release();
			};
		}.start();

		final String fdmPrinter = PathUtils.getSliceFolder()
				+ "/fdmprinter.json";
		new Thread() {
			public void run() {
				try {
					SliceJsonEditor.editSliceJson(IOUtils.readToString(context.getAssets().open("fdmprinter.json")), fdmPrinter, startCode, setting);
				} catch (Exception e) {
					e.printStackTrace();
				}
				semaphore.release();
			};
		}.start();

		String[] command = { "CuraEngine", "slice", "-v", "-j", fdmPrinter,
				"-o", gcodeFile.getAbsolutePath(), "-e1", "-e0", "-l",
				newStlFile.getAbsolutePath() };
		try {
			semaphore.acquire();
			if(!cancelConvert){
				Cura.slice(command, new SliceCallback() {
					public void onProgress(final String type, int current, int total, float percent) {
						if (callback != null && !cancelConvert) {
							handler.post(new Runnable() {
								public void run() {
									callback.onConvertProgress(type, 100);
								}
							});
						}
					}
				});
			}
			
			if (callback != null && !cancelConvert) {
				handler.post(new Runnable() {
					public void run() {
						callback.onConvertSuccess(gcodeFile, null);
					}
				});
			}
		} catch (final Exception e) {
			e.printStackTrace();
			if (callback != null) {
				handler.post(new Runnable() {
					public void run() {
						callback.onConvertFailed(e.getMessage());
					}
				});
			}
		}
	}

	/**
	 * 将Gcode文件转化为X3g文件
	 * 
	 * @param file
	 *            gcode文件
	 * @param machine
	 *            机器参数
	 * @param callback
	 *            转化完成的回调
	 * @return 生成的x3g文件路径
	 */
	public static void gcodeToX3g(final File file, final Machine machine,
			final Callback<File> callback) {
		new Thread(new Runnable() {

			public void run() {
				cancelConvert = false;
				if (callback != null) {
					handler.post(new Runnable() {
						public void run() {
							callback.onPreConvert();
						}
					});
				}
				FileReader fr = null;
				BufferedReader br = null;
				FileOutputStream fos = null;
				BufferedOutputStream bos = null;
				String bufferString = null;
				try {
					fr = new FileReader(file);
					br = new BufferedReader(fr);
					File dir = new File(PathUtils.getGpxFolder());
					if (!dir.exists() && dir.isDirectory()) {
						dir.mkdirs();
					}
					final File x3gFile = new File(PathUtils.getGpxFolder(),
							TMP_X3G);
					fos = new FileOutputStream(x3gFile);
					bos = new BufferedOutputStream(fos);

					final X3gStatus x3gStatus = new X3gStatus(0, 0);
					int lastProgress = 0;
					long fileLength = file.length();
					while ((bufferString = br.readLine()) != null
							&& !cancelConvert) {
						if (bufferString.trim().length() > 0) {
							byte[] datas = Gpx.gpxConverPieceGcode(
									bufferString.trim(), x3gStatus);
							bos.write(datas);
							final int progress = (int) ((x3gFile.length() * 1.0f / fileLength) * 100);
							if (progress != lastProgress) {
								Log.d(TAG, "gcode to x3g  " + progress + "%");
								if (callback != null) {
									handler.post(new Runnable() {
										public void run() {
											callback.onConvertProgress(
													GCODE_TO_X3G, progress);
										}
									});
								}
							}
							lastProgress = progress;
						}
					}
					handler.post(new Runnable() {
						public void run() {
							if (callback != null && !cancelConvert) {
								callback.onConvertSuccess(x3gFile, x3gStatus);
							}
						}
					});

				} catch (Exception e) {
					Log.e(TAG, "ERROR GCODE: " + bufferString);
					handler.post(new Runnable() {
						public void run() {
							if (callback != null) {
								callback.onConvertFailed("convert failed");
							}
						}
					});
				} finally {
					closeStream(br);
					closeStream(fr);
					closeStream(bos);
					closeStream(fos);
				}
			}
		}).start();

	}

	/**
	 * 将Gcode字符串转化为X3g文件
	 * 
	 * @param file
	 *            gcode文件
	 * @param machine
	 *            机器参数
	 * @param callback
	 *            转化完成的回调
	 * @return 生成的x3g文件路径
	 */
	public static void gcodeToX3g(final String gcodeStr, final Machine machine,
			final Callback<byte[]> callback) {
		new Thread(new Runnable() {
			public void run() {
				try {
					cancelConvert = false;
					if (callback != null) {
						handler.post(new Runnable() {
							public void run() {
								callback.onPreConvert();
							}
						});
					}
					final X3gStatus x3gStatus = new X3gStatus(0, 0);
					BufferedReader br = new BufferedReader(new StringReader(gcodeStr));
					ByteArrayOutputStream bos = new ByteArrayOutputStream();
					String gcode;
					while((gcode = br.readLine())!=null){
						byte[] x3gBytes = Gpx.gpxConverPieceGcode(gcode, x3gStatus);
						bos.write(x3gBytes);
					}
					final byte[] bytes = bos.toByteArray();
					handler.post(new Runnable() {
						public void run() {
							if (callback != null) {
								callback.onConvertSuccess(bytes, x3gStatus);
							}
						}
					});

				} catch (Exception e) {
					handler.post(new Runnable() {
						public void run() {
							if (callback != null) {
								callback.onConvertFailed("convert failed");
							}
						}
					});
				}
			}
		}).start();

	}

	/**
	 * 根据InputStream生成文件
	 */
	public static File save2File(InputStream is, String filePath,
			String fileName) {
		BufferedOutputStream bos = null;
		FileOutputStream fos = null;
		File file = null;
		try {
			File dir = new File(filePath);
			if (!dir.exists() && dir.isDirectory()) {// 判断文件目录是否存在
				dir.mkdirs();
			}
			file = new File(filePath, fileName);
			fos = new FileOutputStream(file);
			bos = new BufferedOutputStream(fos);
			byte[] buffer = new byte[BUFFER_SIZE];
			int length = 0;
			while ((length = is.read(buffer)) != -1) {
				bos.write(buffer, 0, length);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			closeStream(is);
			closeStream(bos);
			closeStream(fos);
		}
		return file;
	}

	/**
	 * 根据bytes[]生成文件
	 */
	public static File save2File(byte[] bytes, String filePath, String fileName) {
		BufferedOutputStream bos = null;
		FileOutputStream fos = null;
		File file = null;
		try {
			File dir = new File(filePath);
			if (!dir.exists() && dir.isDirectory()) {// 判断文件目录是否存在
				dir.mkdirs();
			}
			file = new File(filePath, fileName);
			fos = new FileOutputStream(file);
			bos = new BufferedOutputStream(fos);
			bos.write(bytes);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			closeStream(bos);
			closeStream(fos);
		}
		return file;
	}

	public static void cancelConvert() {
		cancelConvert = true;
	}


	protected interface Callback<T> {
		void onPreConvert();

		void onConvertSuccess(T result, X3gStatus x3gStatus);

		void onConvertProgress(String type, int progress);

		void onConvertFailed(String error);
	}

	private static <T extends Closeable> void closeStream(T stream) {
		if (stream != null) {
			try {
				stream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

}